/*
 *  (C) Copyright 2008 Insula Tecnologia da Informacao Ltda.
 * 
 *  This file is part of Vulcano.
 *
 *  Vulcano is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Vulcano is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Vulcano.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.openinsula.vulcano.orm.dao.query.hibernate;

import static org.junit.Assert.*;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.openinsula.vulcano.orm.dao.query.spec.AbstractSingleValuePropertySpec;
import org.openinsula.vulcano.orm.dao.query.spec.AndSpec;
import org.openinsula.vulcano.orm.dao.query.spec.BetweenSpec;
import org.openinsula.vulcano.orm.dao.query.spec.EqualsSpec;
import org.openinsula.vulcano.orm.dao.query.spec.GreaterOrEqualSpec;
import org.openinsula.vulcano.orm.dao.query.spec.GreaterSpec;
import org.openinsula.vulcano.orm.dao.query.spec.InSpec;
import org.openinsula.vulcano.orm.dao.query.spec.LesserOrEqualSpec;
import org.openinsula.vulcano.orm.dao.query.spec.LesserSpec;
import org.openinsula.vulcano.orm.dao.query.spec.LikeSpec;
import org.openinsula.vulcano.orm.dao.query.spec.NotEqualsSpec;
import org.openinsula.vulcano.orm.dao.query.spec.NotNullSpec;
import org.openinsula.vulcano.orm.dao.query.spec.NullSpec;
import org.openinsula.vulcano.orm.dao.query.spec.OrSpec;
import org.openinsula.vulcano.orm.dao.query.spec.Spec;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

public class HqlSpecVisitorImplTestCase {
	private static final Log logger = LogFactory.getLog(HqlSpecVisitorImplTestCase.class);

	private static final HqlSpecVisitorImpl visitor = new HqlSpecVisitorImpl();

	private void assertEquality(final String hqlTemplate, final Spec spec) {
		Method visitMethod = ReflectionUtils.findMethod(visitor.getClass(), "visit", new Class[] { spec.getClass() });

		String actual = (String) ReflectionUtils.invokeMethod(visitMethod, visitor, new Object[] { spec });
		String expected = buildString(hqlTemplate);

		if (logger.isDebugEnabled()) {
			logger.debug("Spec: " + spec.getClass().getSimpleName());
			logger.debug(String.format("expected hql: %s, actual hql: %s", actual, expected));
		}

		assertEquals(expected, actual);

		if (spec instanceof AbstractSingleValuePropertySpec) {
			assertSpecNullableValue((AbstractSingleValuePropertySpec<?>) spec);
		}
	}

	private void assertSpecNullableValue(final AbstractSingleValuePropertySpec<?> spec) {

		if (spec.isNullValueAllowed()) {
			try {
				spec.setValue(null);

			}
			catch (IllegalArgumentException exc) {
				if (logger.isErrorEnabled()) {
					logger.error("Unexpected Exception: " + exc.getMessage());
				}

				fail();
			}
		}
		else {
			try {
				spec.setValue(null);
				fail();
			}
			catch (IllegalArgumentException exc) {
				if (logger.isDebugEnabled()) {
					logger.debug("Expected exception: " + exc.getMessage());
				}
			}
		}

	}

	private String buildString(final String format) {
		String[] args = new String[StringUtils.countOccurrencesOf(format, "%s.")];
		Arrays.fill(args, HibernateVisitorConstants.DEFAULT_ALIAS);

		String result = String.format(format, (Object[]) args);

		if (logger.isDebugEnabled()) {
			logger.debug(String.format("template hql: %s, parsed hql: %s", format, result));
		}

		return result;
	}

	@Test
	public void testVisitAndSpec() {
		EqualsSpec<Integer> equalsSpec = new EqualsSpec<Integer>("age", 0);
		NotEqualsSpec<String> notEqualsSpec = new NotEqualsSpec<String>("name", null);
		GreaterSpec<Date> greaterSpec = new GreaterSpec<Date>("date", new Date());

		assertEquality("(%s.age = ? and %s.name is not null and %s.date > ?)", new AndSpec(equalsSpec, notEqualsSpec,
				greaterSpec));
	}

	@Test
	public void testVisitOrSpec() {
		EqualsSpec<Integer> equalsSpec = new EqualsSpec<Integer>("age", null);
		NotEqualsSpec<String> notEqualsSpec = new NotEqualsSpec<String>("name", "");
		LesserOrEqualSpec<Integer> lesserOrEqualSpec = new LesserOrEqualSpec<Integer>("code", 0);

		assertEquality("(%s.age is null or %s.name <> ? or %s.code <= ?)", new OrSpec(equalsSpec, notEqualsSpec,
				lesserOrEqualSpec));
	}

	@Test
	public void testVisitEqualsSpec() {
		EqualsSpec<Integer> spec = new EqualsSpec<Integer>("code", null);
		assertEquality("%s.code is null", spec);

		spec.setValue(0);
		assertEquality("%s.code = ?", spec);
	}

	@Test
	public void testVisitNotEqualsSpec() {
		NotEqualsSpec<Integer> spec = new NotEqualsSpec<Integer>("code", null);
		assertEquality("%s.code is not null", spec);

		spec.setValue(0);
		assertEquality("%s.code <> ?", spec);
	}

	@Test
	public void testVisitGreaterOrEqualSpec() {
		GreaterOrEqualSpec<Integer> spec = new GreaterOrEqualSpec<Integer>("code", 0);
		assertEquality("%s.code >= ?", spec);
	}

	@Test
	public void testVisitGreaterSpec() {
		GreaterSpec<Integer> spec = new GreaterSpec<Integer>("code", 0);
		assertEquality("%s.code > ?", spec);
	}

	@Test
	public void testVisitLesserOrEqualSpec() {
		LesserOrEqualSpec<Integer> spec = new LesserOrEqualSpec<Integer>("code", 0);
		assertEquality("%s.code <= ?", spec);
	}

	@Test
	public void testVisitLesserSpec() {
		LesserSpec<Integer> spec = new LesserSpec<Integer>("code", 0);
		assertEquality("%s.code < ?", spec);
	}

	@Test
	public void testVisitLikeSpec() {
		LikeSpec spec = new LikeSpec("name", "%John%");
		assertEquality("cast(%s.name, string) like ?", spec);
		assertEquals("%John%", spec.getValue());

		spec = new LikeSpec("name", "%John%", false);
		assertEquality("lower(cast(%s.name, string)) like ?", spec);
		assertEquals("%john%", spec.getValue());
	}

	@Test
	public void testVisitNotNullSpec() {
		assertEquality("%s.code is not null", new NotNullSpec("code"));
	}

	@Test
	public void testVisitNullSpec() {
		assertEquality("%s.code is null", new NullSpec("code"));
	}

	@Test
	public void testVisitBetweenSpec() {
		BetweenSpec spec = new BetweenSpec("code", 0, 0);
		assertEquality("%s.code between ? and ?", spec);
	}

	@Test
	public void testVisitInSpec() {
		InSpec<Integer> spec = new InSpec<Integer>("code", 1, 2, 3, 4, 5);
		assertEquality("%s.code in (?,?,?,?,?)", spec);
	}

}
